#include "stdafx.h"
#include "IrcConnection.h"

//#define COLLABRC_IRC_DEBUG

CIrcConnection::CIrcConnection(void):m_socket(NULL)
{
}

CIrcConnection::~CIrcConnection(void)
{
}

void CIrcConnection::CommandWaiting()
{
	m_pendingCommandsLock.lock();
	m_messagesPending.wakeAll();
	m_pendingCommandsLock.unlock();
}

void CIrcConnection::EstablishConnection()
{
	CLoggedEvent *connection = new CLoggedEvent(CLoggedEvent::PROGRESS, "Initializing", "Connecting to channel...");
	emit LogEvent(connection);	

	delete m_socket;
	m_socket = new QTcpSocket;
	// Connect to IRC
	m_socket->connectToHost(GetHost(), GetPort());
	m_socket->setTextModeEnabled(true);
	connect(m_socket, SIGNAL(readyRead()), this, SLOT(CommandWaiting()));

	if (!m_socket->waitForConnected())
	{
		// TODO: Handle this gracefully
		QString error = "Could not connect to collaboration channel: ";
		error += m_socket->errorString();
		qDebug(error.toStdString().c_str());
		connection->UpdateStatus(CLoggedEvent::FATALERROR);
		connection->UpdateMessage(m_socket->errorString());
		connection->deleteLater();

		return;
	}

	QString nick = GetUser();
	QString user = "USER " + nick + " 8 * " + nick + "\r\n";
	nick = "NICK " + nick + "\r\n";

	m_socket->write(user.toAscii());
	m_socket->write(nick.toAscii());
	if (!m_socket->waitForBytesWritten())
	{
		QString error = "Could not connect to collaboration channel: ";
		error += m_socket->errorString();
		qDebug(error.toStdString().c_str());

		connection->UpdateStatus(CLoggedEvent::FATALERROR);
		connection->UpdateMessage(m_socket->errorString());

		connection->deleteLater();
		return EstablishConnection();
	}

	// Wait for welcome message
	for (QString line; !line.contains("001"); line = m_socket->readLine())
	{
		if (line.isEmpty() && !m_socket->waitForReadyRead())
		{
			connection->deleteLater();
			return EstablishConnection();
		}
#ifdef COLLABRC_IRC_DEBUG
		else if (!line.isEmpty())
		{
			qDebug(line.toAscii());
		}
#endif
	}

	QString join = "JOIN " + GetChannel() + "\r\n";
	m_socket->write(join.toAscii());
	if (!m_socket->waitForBytesWritten())
	{
		QString error = "Could not connect to collaboration channel: ";
		error += m_socket->errorString();
		qDebug(error.toStdString().c_str());

		connection->UpdateStatus(CLoggedEvent::FATALERROR);
		connection->UpdateMessage(m_socket->errorString());

		connection->deleteLater();
		return EstablishConnection();
	}

	// Wait for the join event
	for (QString line; !line.contains("366"); line = m_socket->readLine())
	{
		if (line.isEmpty() && !m_socket->waitForReadyRead())
		{
			connection->deleteLater();
			return EstablishConnection();
		}
#ifdef COLLABRC_IRC_DEBUG
		else if (!line.isEmpty())
		{
			qDebug(line.toAscii());
		}
#endif
	}

	// Update to connected state
	connection->UpdateStatus(CLoggedEvent::COMPLETE);
	connection->UpdateMessage("Connected to collaboration channel");
	connection->deleteLater();

	emit ConnectionEstablished();
}

void CIrcConnection::HandleIncomingCommand(QString command)
{
#ifdef COLLABRC_IRC_DEBUG
	QString msg = "--> " + command;
	qDebug(msg.trimmed().toStdString().c_str());
#endif

	if (command.startsWith("PING :"))
	{
		// Send PONG
		QString response = "PONG :";
		response += command.mid(6);
		QueueCommand(response);
	}
	else
	{
		return ProcessIncomingCommand(command);
	}
}

void CIrcConnection::ConnectionLost()
{
	// TODO: NYI
}

void CIrcConnection::Disconnect()
{
	m_running = false;
	this->wait();
	delete m_socket;
}

void CIrcConnection::run()
{
	int id = 0;
	qDebug("Collaboration thread running");

	EstablishConnection();
	CLoggedEvent readyEvent(CLoggedEvent::INFO, "Ready", "System ready");
	emit LogEvent(&readyEvent);

	// Enter loop
	m_running = true;

	while (m_running)
	{
		// Pick the next message
		QString str;
		QString received;
		// Do we have another command?
		m_pendingCommandsLock.lock();
		try 
		{
			while (m_pendingCommands.size() == 0 && !m_socket->canReadLine())
				if (!m_socket->waitForReadyRead(0))
					m_messagesPending.wait(&m_pendingCommandsLock, 100);

			if (m_pendingCommands.size() > 0)
			{
				// Pull out the highest priority command and run it
				str = m_pendingCommands.dequeue();
			}
			else 
			{
				str = "";
			}
			m_pendingCommandsLock.unlock();

		}
		catch (...)
		{
			m_pendingCommandsLock.unlock();
			throw;
		}


		if (!str.isEmpty())
		{
#ifdef COLLABRC_IRC_DEBUG
			QString debug = "<-- " + str;
			qDebug(debug.toStdString().c_str());
#endif
			// Run the command
			str += "\r\n";
			if (m_socket->write(str.toStdString().c_str()) <= 0)
			{
				// An error occurred
				QString error = "Failed to write command: " + str;
				qDebug(error.toStdString().c_str());
				qDebug(m_socket->errorString().toStdString().c_str());
			}
			else 
			{
				bool success = m_socket->waitForBytesWritten();
				if (!success)
				{
					QString error = "Failed to write command: " + str;
					qDebug(error.toStdString().c_str());
					qDebug(m_socket->errorString().toStdString().c_str());
				}
			}
		}

		// Handle anything to receive
		QString line = m_socket->readLine();
		if (!line.isEmpty())
			HandleIncomingCommand(line);
	}

	// Disconnect gracefully
	// TODO: Fill version dynamically
	m_socket->write("QUIT :CollabRC v0.1 - By Matthew 'Shirik' Del Buono\r\n");
	m_socket->waitForDisconnected(5000);	
}




void CIrcConnection::QueueCommand( QString command, bool highPriority )
{
	m_pendingCommandsLock.lock();
	try
	{
		if (highPriority)
			m_pendingCommands.insert(0, command);
		else
			m_pendingCommands.enqueue(command);

		m_messagesPending.wakeAll();
		m_pendingCommandsLock.unlock();
	}
	catch (...)
	{
		m_pendingCommandsLock.unlock();
		throw;
	}
}
